#include <iostream>
#include <thread>
#include <vector>
#include <random>
#include <chrono>
#include <algorithm>
#include <iomanip>
#include "safe_ptr.h"


struct user_accounts_t { 
    std::string user_name; int64_t money; 
    user_accounts_t(std::string u, int64_t m) : user_name(u), money(m) {}
};

safe_ptr<std::map<uint64_t, user_accounts_t >> safe_user_accounts(
    std::map<uint64_t, user_accounts_t >({
    std::make_pair(1, user_accounts_t("John Smith", 100)),
    std::make_pair(2, user_accounts_t("John Rambo", 150))
}));

struct cash_flows_t { uint64_t unique_id, src_id, dst_id, time; int64_t money; };

std::atomic<uint64_t> global_unique_id;
safe_ptr<std::multimap<uint64_t, std::shared_ptr<cash_flows_t>>> safe_cash_flows_src_id;
safe_ptr<std::multimap<uint64_t, std::shared_ptr<cash_flows_t>>> safe_cash_flows_dst_id;

// too much granularity (very slow) 
//static link_safe_ptrs tmp_link(safe_user_accounts, safe_cash_flows_src_id, safe_cash_flows_dst_id); 


void move_money(uint64_t src_id, uint64_t dst_id, uint64_t time, int64_t money)
{
    auto cash_flow_row_ptr = std::make_shared<cash_flows_t>();
    cash_flow_row_ptr->unique_id = ++global_unique_id;
    cash_flow_row_ptr->src_id = src_id;
    cash_flow_row_ptr->dst_id = dst_id;
    cash_flow_row_ptr->time = time;
    cash_flow_row_ptr->money = money;

    std::cout << " - start transaction... move_money() \n";
    lock_timed_any_infinity lock_all(safe_cash_flows_src_id, safe_cash_flows_dst_id, safe_user_accounts);   // 2, 3, 1
        
    // update table-1
    safe_user_accounts->at(src_id).money -= money;            
    safe_user_accounts->at(dst_id).money += money;

    // inset to indexes in table safe_cash_flows: src & dst
    safe_cash_flows_src_id->emplace(src_id, cash_flow_row_ptr);
    safe_cash_flows_dst_id->emplace(dst_id, cash_flow_row_ptr);
 
    std::cout << " - end transaction: move_money() \n";
}

void show_total_amount() 
{
        int64_t total_amount = 0;

        std::cout << " - start transaction... show_total_amount() \n";
        lock_timed_any_infinity lock_all(safe_user_accounts);            // 1
    
        std::cout << std::endl;
        for (auto it = safe_user_accounts->begin(); it != safe_user_accounts->end(); ++it) {
            total_amount += it->second.money;
            std::cout << it->first << " => " << it->second.user_name << ", " <<
                it->second.money << std::endl;
        }
        std::cout << "Result: all accounts total_amount = " << total_amount << " \t <<< \n\n";

        std::cout << " - end transaction: show_total_amount() \n";
}


void show_user_money_on_time(uint64_t user_id, uint64_t time)
{
        int64_t incoming = 0;
        int64_t outcoming = 0;
        int64_t user_money = 0;
        std::string user_name;

        std::cout << " - start transaction... show_user_money_on_time() \n";
        lock_timed_any_infinity lock_all(safe_cash_flows_dst_id, safe_cash_flows_src_id, safe_user_accounts);     // 3, 2, 1   

        std::cout << std::endl;
        auto in_range = safe_cash_flows_dst_id->equal_range(user_id);
        for (auto it = in_range.first; it != in_range.second; ++it)
            if (it->second->time > time)
                incoming += it->second->money;

        auto out_range = safe_cash_flows_src_id->equal_range(user_id);
        for (auto it = out_range.first; it != out_range.second; ++it)
            if (it->second->time > time)
                outcoming += it->second->money;

        user_money = safe_user_accounts->at(user_id).money;
        user_name = safe_user_accounts->at(user_id).user_name;

        std::cout << std::endl << "incoming = " << incoming << ", outcoming = " << outcoming <<
            ", current user_money = " << user_money << std::endl;

        user_money = user_money - incoming + outcoming; // take into account cash flow

        std::cout << user_id << " => " << user_name << ", " << user_money <<
            ", at time = " << time << " \t <<< \n\n";

        std::cout << " - end transaction: show_user_money_on_time() \n";
}

int main() {

    std::cout << "Init table safe_user_accounts: " << std::endl;
    std::cout << "at time = 0  \t\t <<< " << std::endl;

    for (auto it = safe_user_accounts->begin(); it != safe_user_accounts->end(); ++it)
        std::cout << it->first << " => " << it->second.user_name << ", " <<
        it->second.money << std::endl;
    std::cout << std::endl;


    std::thread t1([&]() { move_money(2, 1, 12, 50); });    // src_id, dst_id, time, money

    std::thread t2([&]() { show_total_amount(); });

    std::thread t3([&]() { show_user_money_on_time(1, 0); });   // user_id, time

    t1.join();
    t2.join();
    t3.join();

    std::cout << "end";
    int b; std::cin >> b;

    return 0;
}